package org.carabiner.infinitest;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.carabiner.infinitest.depgraph.DependencyGraph;
import org.carabiner.infinitest.depgraph.JavaClass;
import org.carabiner.infinitest.swingui.EventSupport;
import org.carabiner.infinitest.swingui.ResultTreeModel;

/**
 * Core class for Infinitest.
 * 
 * @author <a href="mailto:benrady@gmail.com"Ben Rady</a>
 */
public class DefaultInfinitestCore implements InfinitestCore
{
  public static final String TESTS_IN_QUEUE_PROPERTY = "Queued Tests";
  public static final String CURRENT_TEST_PROPERTY = "Current Test";
  public static final String STATUS_PROPERTY = "status";

  private File filterFile;
  private TestRunner runner;
  private DependencyGraph index;
  private FilterList filterList;
  private boolean runTests = true;
  private PropertyChangeSupport pcs;
  private FileChangeDetector changeDetector;
  private Status status;
  private Timer timer;
  private ResultTreeModel testResults;
  private long lastGreenBar;

  public DefaultInfinitestCore(File filterListFile, List<File> classDirList) throws IOException
  {
    if (filterListFile == null)
      filterFile = new File("infinitest.filters");
    else
      filterFile = filterListFile;

    lastGreenBar = System.currentTimeMillis();
    status = Status.SCANNING;
    pcs = new PropertyChangeSupport(this);

    filterList = new FilterList(filterFile);
    changeDetector = new FileChangeDetector(classDirList);
    index = new DependencyGraph(classDirList, filterList);

    testResults = new ResultTreeModel();
    addTestStatusListener(EventSupport.eventNormalizer(testResults));
  }

  public boolean shouldRunTests()
  {
    return runTests;
  }

  public void setRunTests(boolean shouldRunTests)
  {
    runTests = shouldRunTests;
    if (runTests)
    {
      reload();
    }
    else
    {
      setStatus(Status.PAUSED);
      getRunner().killTest();
    }
  }

  public void update()
  {
    setStatus(Status.SCANNING);
    Set<JavaClass> testsToRun;
    try
    {
      testsToRun = index.findTestsToRun(changeDetector.findChangedFiles());
    }
    catch (IOException ex)
    {
      throw new RuntimeException(ex);
    }
    if (runTests)
    {
      if (!testsToRun.isEmpty())
      {
        pcs.firePropertyChange(TESTS_IN_QUEUE_PROPERTY, 0, testsToRun.size());
        Set<String> testNames = getNames(testsToRun);
        int testsLeft = testsToRun.size();
        for (String testName : testNames)
        {
          setStatus(Status.RUNNING);
          pcs.firePropertyChange(CURRENT_TEST_PROPERTY, null, testName);
          getRunner().runTest(testName);
          pcs.firePropertyChange(TESTS_IN_QUEUE_PROPERTY, testsLeft, --testsLeft);
          pcs.firePropertyChange(CURRENT_TEST_PROPERTY, testName, null);
          if (!runTests)
          {
            break;
          }
        }
      }
    }
    setStatus(computeStatus());
  }

  public TestRunner getRunner()
  {
    if (runner == null)
      runner = new SimpleProcessRunner(JUnit4Runner.class);
    return runner;
  }
  
  private Status computeStatus()
  {
    if (!runTests)
      return Status.PAUSED;
    if (testResults.isAnyFailures())
      return Status.FAILING;
    return Status.PASSING;
  }

  private void setStatus(Status newStatus)
  {
    Status oldStatus = status;
    status = newStatus;
    if (status.equals(Status.PASSING))
      lastGreenBar = getCurrentTime();
    pcs.firePropertyChange(STATUS_PROPERTY, oldStatus, status);
  }

  Set<String> getNames(Set<JavaClass> testsToRun)
  {
    Set<String> names = new HashSet<String>();
    for (JavaClass clazz : testsToRun)
    {
      names.add(clazz.getName());
    }
    return names;
  }

  public FilterList getFilters()
  {
    return filterList;
  }

  public Status getStatus()
  {
    return status;
  }

  public void addPropertyChangeListener(String propertyName, PropertyChangeListener l)
  {
    if (propertyName != null)
      pcs.addPropertyChangeListener(propertyName, l);
    else
      pcs.addPropertyChangeListener(l);
  }

  public void addTestStatusListener(TestStatusListener l)
  {
    getRunner().addTestStatusListener(l);
  }

  public void reload()
  {
    setStatus(Status.SCANNING);
    index.clear();
    changeDetector.clear();
  }

  public void dispose()
  {
    timer.cancel();
  }

  public void addPropertyChangeListener(PropertyChangeListener listener)
  {
    pcs.addPropertyChangeListener(listener);
  }

  public ResultTreeModel getTestResultModel()
  {
    return testResults;
  }

  public long getTimeSincePassingState()
  {
    return getCurrentTime() - lastGreenBar;
  }

  protected long getCurrentTime()
  {
    return System.currentTimeMillis();
  }
}
